/***
*malloc.c - Get a block of memory from the heap
*
*       Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.
*
*Purpose:
*       Defines the malloc() function.
*
*******************************************************************************/

#include <cruntime.h>
#include <malloc.h>
#include <internal.h>
#include <mtdll.h>
#include <dbgint.h>

#ifdef WINHEAP
#include <windows.h>
#include <winheap.h>
#else  /* WINHEAP */
#include <heap.h>
#endif  /* WINHEAP */


extern int _newmode;    /* malloc new() handler mode */


/***
*void *malloc(size_t size) - Get a block of memory from the heap
*
*Purpose:
*       Allocate of block of memory of at least size bytes from the heap and
*       return a pointer to it.
*
*       Calls the new appropriate new handler (if installed).
*
*Entry:
*       size_t size - size of block requested
*
*Exit:
*       Success:  Pointer to memory block
*       Failure:  NULL (or some error value)
*
*Uses:
*
*Exceptions:
*
*******************************************************************************/

void * __cdecl _malloc_base (size_t size)

{
        return _nh_malloc_base(size, _newmode);
}


/***
*void *_nh_malloc_base(size_t size) - Get a block of memory from the heap
*
*Purpose:
*       Allocate of block of memory of at least size bytes from the heap and
*       return a pointer to it.
*
*       Calls the appropriate new handler (if installed).
*
*       There are two distinct new handler schemes supported. The 'new' ANSI
*       C++ scheme overrides the 'old' scheme when it is activated. A value of
*       _NOPTH for the 'new' handler indicates that it is inactivated and the
*       'old' handler is then called.
*
*Entry:
*       size_t size - size of block requested
*
*Exit:
*       Success:  Pointer to memory block
*       Failure:  NULL (or some error value)
*
*Uses:
*
*Exceptions:
*
*******************************************************************************/

void * __cdecl _nh_malloc_base (size_t size, int nhFlag)
{
        void * pvReturn;

        //  validate size
        if (size > _HEAP_MAXREQ)
            return NULL;

#ifndef WINHEAP
        /* round requested size */
        size = _ROUND2(size, _GRANULARITY);
#endif  /* WINHEAP */

        for (;;) {

            //  allocate memory block
            if (size <= _HEAP_MAXREQ)
                pvReturn = _heap_alloc_base(size);
            else
                pvReturn = NULL;

            //  if successful allocation, return pointer to memory
            //  if new handling turned off altogether, return NULL

            if (pvReturn || nhFlag == 0)
                return pvReturn;

            //  call installed new handler
            if (!_callnewh(size))
                return NULL;

            //  new handler was successful -- try to allocate again
        }
}

/***
*void *_heap_alloc_base(size_t size) - does actual allocation
*
*Purpose:
*       Same as malloc() except the new handler is not called.
*
*Entry:
*       See malloc
*
*Exit:
*       See malloc
*
*Exceptions:
*
*******************************************************************************/

void * __cdecl _heap_alloc_base (size_t size)

{
#ifdef WINHEAP
        void * pvReturn;
#else  /* WINHEAP */
        _PBLKDESC pdesc;
        _PBLKDESC pdesc2;
#endif  /* WINHEAP */


#ifdef WINHEAP

        if (size <= __sbh_threshold)
        {
            _mlock(_HEAP_LOCK);
            pvReturn = __sbh_alloc_block(size);
            _munlock(_HEAP_LOCK);
            if (pvReturn)
                return pvReturn;
        }

        if (size == 0)
            size = 1;
        size = (size + BYTES_PER_PARA - 1) & ~(BYTES_PER_PARA - 1);
        return HeapAlloc(_crtheap, 0, size);
}

#else  /* WINHEAP */

        /* try to find a big enough free block
         */
        if ( (pdesc = _heap_search(size)) == NULL )
        {
            if ( _heap_grow(size) != -1 )
            {
                /* try finding a big enough free block again. the
                 * success of the call to _heap_grow should guarantee
                 * it, but...
                 */
                if ( (pdesc = _heap_search(size)) == NULL )
                {
                    /* something unexpected, and very bad, has
                     * happened. abort!
                     */
                    _heap_abort();
                }
            }
            else
                return NULL;
        }

        /* carve the block into two pieces (if necessary). the first piece
         * shall be of the exact requested size, marked inuse and returned to
         * the caller. the leftover piece is to be marked free.
         */
        if ( _BLKSIZE(pdesc) != size ) {
            /* split up the block and free the leftover piece back to
             * the heap
             */
            if ( (pdesc2 = _heap_split_block(pdesc, size)) != NULL )
                _SET_FREE(pdesc2);
        }

        /* mark pdesc inuse
         */
        _SET_INUSE(pdesc);

        /* check proverdesc and reset, if necessary
         */

        _heap_desc.proverdesc = pdesc->pnextdesc;

        return( (void *)((char *)_ADDRESS(pdesc) + _HDRSIZE) );
}


/***
*_PBLKDESC _heap_split_block(pdesc, newsize) - split a heap allocation block
*       into two allocation blocks
*
*Purpose:
*       Split the allocation block described by pdesc into two blocks, the
*       first one being of newsize bytes.
*
*       Notes: It is caller's responsibilty to set the status (i.e., free
*       or inuse) of the two new blocks, and to check and reset proverdesc
*       if necessary. See Exceptions (below) for additional requirements.
*
*Entry:
*       _PBLKDESC pdesc - pointer to the allocation block descriptor
*       size_t newsize  - size for the first of the two sub-blocks (i.e.,
*                 (i.e., newsize == _BLKSIZE(pdesc), on exit)
*
*Exit:
*       If successful, return a pointer to the descriptor for the leftover
*       block.
*       Otherwise, return NULL.
*
*Exceptions:
*       It is assumed pdesc points to a valid allocation block descriptor and
*       newsize is a valid heap block size as is (i.e., WITHOUT rounding). If
*       either of these of assumption is violated, _heap_split_block() will
*       likely corrupt the heap. Note also that _heap_split_block will simply
*       return to the caller if newsize >= _BLKSIZE(pdesc), on entry.
*
*******************************************************************************/

_PBLKDESC __cdecl _heap_split_block (
        REG1 _PBLKDESC pdesc,
        size_t newsize
        )
{
        REG2 _PBLKDESC pdesc2;

        _ASSERTE(("_heap_split_block: bad pdesc arg", _CHECK_PDESC(pdesc)));
        _ASSERTE(("_heap_split_block: bad newsize arg", _ROUND2(newsize,_GRANULARITY) == newsize));

        /* carve the block into two pieces (if possible). the first piece
         * is to be exactly newsize bytes.
         */
        if ( (_BLKSIZE(pdesc) > newsize) && ((pdesc2 = __getempty())
               != NULL) )
        {
            /* set it up to manage the second piece and link it in to
             * the list
             */
            pdesc2->pblock = (void *)((char *)_ADDRESS(pdesc) + newsize +
                     _HDRSIZE);
            *(void **)(pdesc2->pblock) = pdesc2;
            pdesc2->pnextdesc = pdesc->pnextdesc;
            pdesc->pnextdesc = pdesc2;

            return pdesc2;
        }
        return NULL;
}

#endif  /* WINHEAP */

